package checker

import (
	"kumachan/interpreter/lang/ast"
	"kumachan/interpreter/lang/common/source"
	"kumachan/interpreter/compiler/checker/typsys"
	"kumachan/interpreter/compiler/checker/checked"
)


type ExprChecker func (
	expected typsys.Type, s *typsys.InferringState, ctx ExprContext) (
	*checked.Expr, *typsys.InferringState, *source.Error,
)
func makeExprChecker(loc source.Location, k func(*checkContext)(checkResult)) ExprChecker {
	return func(expected typsys.Type, s *typsys.InferringState, ctx ExprContext) (*checked.Expr, *typsys.InferringState, *source.Error) {
		var cc = makeCheckContext(loc, expected, s, ctx)
		var result = k(cc)
		return result.expr, cc.inferring, result.err
	}
}
func makeExprCheckerWithLocalScope(loc source.Location, k func(*checkContextWithLocalScope)(checkResult)) ExprChecker {
	return func(expected typsys.Type, s *typsys.InferringState, ctx ExprContext) (*checked.Expr, *typsys.InferringState, *source.Error) {
		var cc = makeCheckContextWithLocalScope(loc, expected, s, ctx)
		var result = k(cc)
		return result.expr, cc.inferring, result.err
	}
}

func check(node ast.Expr) ExprChecker {
	return ExprChecker(func(expected typsys.Type, s *typsys.InferringState, ctx ExprContext) (*checked.Expr, *typsys.InferringState, *source.Error) {
		var L = len(node.Pipeline)
		if L == 0 {
			return checkTerm(node.Term)(expected, s, ctx)
		} else {
			var last_index = (L - 1)
			var rest_pipeline = node.Pipeline[:last_index]
			var last = node.Pipeline[last_index]
			var rest = ast.Expr {
				Node:     node.Node,
				Term:     node.Term,
				Pipeline: rest_pipeline,
			}
			var cast, is_cast = last.Pipe.(ast.PipeCast)
			if is_cast {
				var loc = last.Location
				return checkCast(rest, cast.Target, loc)(expected, s, ctx)
			} else {
				var rest_expr, _, err = check(rest)(nil, nil, ctx)
				if err != nil { return nil, nil, err }
				return checkPipe(rest_expr, last)(expected, s, ctx)
			}
		}
	})
}
func checkPipe(in *checked.Expr, pipe ast.VariousPipe) ExprChecker {
	var loc = pipe.Location
	switch P := pipe.Pipe.(type) {
	case ast.PipeCast:
		panic("branch should have been handled elsewhere")
	case ast.PipeFunc:
		return checkCallPipe(P.Callee, in, P.Argument, loc)
	case ast.PipeGet:
		return checkGet(in, P.Key, loc)
	case ast.PipeInterior:
		return checkInterior(in, P.Key, loc)
	default:
		panic("impossible branch")
	}
}
func checkTerm(term ast.VariousTerm) ExprChecker {
	switch T := term.Term.(type) {
	case CheckedExprTerm:
		return checkCheckedExpr(T)
	case ConstructorCallTerm:
		return checkConstructorCall(T, term.Location)
	case ast.VariousCall:
		switch C := T.Call.(type) {
		case ast.CallPrefix:
			return checkCallPrefix(C.Callee, C.Argument, C.Location)
		case ast.CallInfix:
			return checkCallInfix(C.Operator, C.Left, C.Right, C.Location)
		case ast.CallCps:
			return checkCallCps(C.Callee, C.Input, C.Binding, C.Output, C.Node)
		default:
			panic("impossible branch")
		}
	case ast.TermBlank:
		return checkBlank(T)
	case ast.StringLiteral:
		return checkString(T)
	case ast.Formatter:
		return checkFormatter(T)
	case ast.CharLiteral:
		return checkChar(T)
	case ast.FloatLiteral:
		return checkFloat(T)
	case ast.IntegerLiteral:
		return checkInteger(T)
	case ast.InlineRef:
		return checkInlineRef(T)
	case ast.Tuple:
		return checkTuple(T)
	case ast.Record:
		return checkRecord(T)
	case ast.Lambda:
		return checkLambda(T, nil)
	case ast.Block:
		return checkBlock(T)
	case ast.Array:
		return checkList(T)
	case ast.If:
		return checkIf(T)
	case ast.Switch:
		return checkSwitch(T)
	case ast.Select:
		return checkSelect(T)
	case ast.PipelineLambda:
		return checkPipelineLambda(T)
	case ast.ConstructorLambda:
		return checkConstructorLambda(T)
	default:
		panic("impossible branch")
	}
}


